package com.marketlive.biz.session.context;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.marketlive.biz.account.IAccountManager;
import org.marketlive.biz.session.context.ICommerceSession;
import org.marketlive.biz.session.context.ICommerceSessionManager;
import org.marketlive.biz.session.context.ILoginExtension;
import org.marketlive.biz.session.context.ILogoutExtension;
import org.marketlive.biz.session.context.IRegisterCustomerExtension;
import org.marketlive.entity.IPrimaryKey;
import org.marketlive.entity.account.ICustomer;
import org.marketlive.entity.session.ISessionSummary;


import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;

/**
 * CommerceSession holds the state for the shopper's visit.  Decisions that are based
 * on session-related type information can then get everything from a single place.
 * This also makes it possible to simulate sessions in the past or future.
 */
public class CommerceSession implements ICommerceSession {

    /**
     * Logger.
     */
    protected static Log log = LogFactory.getLog(CommerceSession.class);
    /**
     * Unique session id.
     */
    private Integer id;
    /**
     * The CommerceSessionManager. Always use getSessionManager() instead
     * of directly accessing the manager through this variable.
     */
    private transient ICommerceSessionManager sessionManager;
    /**
     * Basket primary key.
     */
    private IPrimaryKey basketPk;
    /**
     * Customer primary key.
     */
    private IPrimaryKey customerPk;

    /**
     * The Customer.  Always use the getCustomer() accessor instead of directly accessing
     * via this transient variable.
     */
    private transient ICustomer customer;
    /**
     * Logged-in flag.  Logged-in users have access to restricted areas of the site including My Account and Checkout.
     */
    private boolean loggedIn;
    /**
     * Holder for summary information about the session.
     */
    private ISessionSummary sessionSummary;
    /**
     * Starts the starting time for the session.  Used to calculate time on site.
     */
    private long sessionStartTime;
    /**
     * Source codes associated with this session.
     */
    private List<IPrimaryKey> sourceCodePks = new ArrayList<IPrimaryKey>();
    /**
     * Map of surveys that have been displayed to customer keyed on survey id.
     */
    private Map surveysPresented;
    /**
     * Map of surveys that have been submitted by customer keyed on survey id.
     */
    private Map surveysSubmitted;

    private long lastRequestStartTime;
    private long lastRequestFinishTime;

    private HashMap sessionVars = new HashMap();

    private Locale locale;
    private HttpServletRequest currentRequest;

    /**
     * The ABTest values: TestID, VerisonID and testDuration
     */
    private String abtestID;
    private String abtestVersionID;
    private int abtestDuration;

    /**
     * Defines if session was finalized.
     */
    private boolean finalized = false;

	/**
     * Creates new CommerceSession
     */
    public CommerceSession(ICommerceSessionManager sessionManager, Integer id) {

        setCommerceSessionManager(sessionManager);
        this.id = id;

        lastRequestStartTime = -1;
        lastRequestFinishTime = -1;

        sessionStartTime = System.currentTimeMillis();

        loggedIn = false;

    }

    public Integer getId() {
        return id;
    }

    public void setCommerceSessionManager(ICommerceSessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public final ICommerceSessionManager getCommerceSessionManager() {
        return sessionManager;
    }

    /**
     * Gets date / time of session.  This is here so that all conditions relevant
     * to a session are in a single object. Decisions that are based on session-related
     * type information can then get everything from a single place, making it possible
     * to simulate sessions in the past or future.
     */
    public long getCurrentTime() {
        return System.currentTimeMillis();
    }

    /**
     * Time of the users last request to the site.  This is set at the beginning of
     * each request.
     * <p/>
     * If this is the first request then -1 is returned
     */
    public long getLastRequestStartTime() {
        return lastRequestStartTime;
    }

    /**
     * Time of the users last request to the site.  This is set at the end of
     * each request, so calling getLastRequestTime() during a request will return
     * the time the previous request was sent.
     * <p/>
     * If this is the first request then -1 is returned
     */
    public long getLastRequestFinishTime() {
        return lastRequestFinishTime;
    }

    public ISessionSummary getSessionSummary() {
        return sessionSummary;
    }

    public void setSessionSummary(ISessionSummary sessionSummary) {
        this.sessionSummary = sessionSummary;
    }

    /**
     * Returns true if the customer has logged in
     */
    public boolean isLoggedIn() {
        return loggedIn;
    }

    /**
     * Determine if a customer is registered
     *
     * @return true if the customer is registered
     */
    public boolean isRegistered() {
        ICustomer customer = getCustomer();

        return customer != null && !customer.isGuest();
    }

    public boolean isGuest() {
        ICustomer customer = getCustomer();

        return customer == null || customer.isGuest();
    }

    /**
     * Attempts to login the customer within the specified user id and password.
     * Returns true if the login was successful.  A successful login will also
     * set the customer.
     */
    public boolean login(String userId, String password) throws Exception {
        boolean valid = false;

		// Iterate through the list of login extensions.
	    Iterator iter = getCommerceSessionManager().getLoginExtensions().iterator();
	    while (iter.hasNext()) {
	        ILoginExtension loginExtension = (ILoginExtension) iter.next();
	        int loginStatus = loginExtension.login(this, userId, password);
	        if (loginStatus == ILoginExtension.LOGIN_PASS) {
	        	loggedIn = true;
	        	return true;
	        }
	        if (loginStatus == ILoginExtension.LOGIN_FAIL) {
	        	return false;
	        }
	    }

        return valid;
    }

    /**
     * Logins in the user as a guest.  If the current customer is registered then a new
     * customer will be created and assigned to the session.  If the current customer
     * is unregistered then it is used.  The customer will be logged in upon completion
     * of this call.
     */
    public void loginGuest() {
        loggedIn = true;
    }

    /**
     * Logs out the customer
     *
     */
    public void logout() {
        loggedIn = false;

		// Iterate through the list of logout extensions.
	    Iterator iter = getCommerceSessionManager().getLogoutExtensions().iterator();
	    while (iter.hasNext()) {
	        ILogoutExtension logoutExtension = (ILogoutExtension) iter.next();
	        logoutExtension.logout(this);
	    }

        // todo: log the logout event
    }

    /**
     * Registers the customer with the specified user name and password.  If the current
     * customer is already a registered user then a new user is created as part of the call.
     * If the current customer is a guest user then it is assigned the user name and password.
     *
     * @param userName the user name - currently the user email and user name are one and the same
     * @param password the user password
     * @return the newly registered customer; null if the operation failed.
     */
    public ICustomer register(String userName, String password) {
        final IAccountManager accountManager = getCommerceSessionManager().getAccountManager();
        ICustomer customer = getCustomer();

        if (customer.isGuest()) {
            accountManager.updateCustomer(customer, userName, userName, password, false);
        }
        else {
            customer = getCustomer();
        }

        loggedIn = true;

		// Iterate through the list of register customer extensions.
	    Iterator iter = getCommerceSessionManager().getRegisterCustomerExtensions().iterator();
	    while (iter.hasNext()) {
	        IRegisterCustomerExtension registerCustomerExtension = (IRegisterCustomerExtension) iter.next();
	        registerCustomerExtension.registerCustomer(this, userName, password);
	    }

        return customer;
    }

    /**
     *
     */
    public IPrimaryKey getCustomerPk() {
        return customerPk;
    }

    /**
     * Sets the customer pk and clears the customer.
     * @param customerPk
     */
    public void setCustomerPk(IPrimaryKey customerPk) {
        this.customerPk = customerPk;
    }

    /**
     * Get basketPk.
     * @return basketPk
     */
    public IPrimaryKey getBasketPk() {
        return basketPk;
    }

    /**
     * Sets the basketPk and sets basket to null to be reloaded on first call to getBasket();
     * @param basketPk
     */
    public void setBasketPk(IPrimaryKey basketPk) {
        this.basketPk = basketPk;
    }

    /**
     * Returns the current customer if one exists.
     */
    public ICustomer getCustomer() {
        return getCustomer(false);
    }

    /**
     * Returns the customer.  If create is 'true' a customer is created if none is assigned or the assigned customer is
     * invalid.  If create is false and there is not a valid customer <code>null</code> is returned.
     *
     * @param createIfRequired
     */
    public ICustomer getCustomer(boolean createIfRequired) {
        // if we have the customer, return it
        if (this.customer != null) {
            return this.customer;
        }

        // if no pk, and no create, return null
        if (this.customerPk == null && !createIfRequired) {
            return null;
        }

        // if we have a pk, get the customer
        if (this.customerPk != null) {
            this.customer = getAccountManager().findCustomerByPk(this.customerPk.getAsString());
        }

        // if found return
        if (this.customer != null) {
            return this.customer;
        } else {
            log.info("Error finding customer: " + this.customerPk );
            this.customerPk = null;
        }

        // if not found, and no create return null
        if (!createIfRequired) {
            return null;
        }

        // customer is null, and create is true, create a new one
        // customer = (ICustomer) getCustomerHome().create(null,null,null);
        this.customer = getAccountManager().createCustomer(null,null,null,null,false);
        this.customerPk = this.customer.getPk();
        return this.customer;
    }

    public void setCustomer(ICustomer customer) {
        log.debug("setCustomer(): " + customer);
        if (customer != null) {
            customerPk = customer.getPk();
        }
        else {
            customerPk = null;
        }
        this.customer = customer;
    }

    /**
     * Starts a new basket, abandoning the old basket.
     */
    public void startNewBasket() {
        setBasketPk(null);
    }

    /**
     * {@inheritDoc}
     */
    public List<IPrimaryKey> getSourceCodePks() {
        return sourceCodePks;
    }

    /**
     * Returns IDs of all Surveys presented during this session as a Collection of Integers
     */
    public Collection getSurveyPresentedIds() {
        return surveysPresented.keySet();
    }

    /**
     * Returns a map of all Surveys presented during this session keyed on Integer id
     */
    public Collection getSurveysPresented() {
        return surveysPresented.values();
    }

    /**
     * Adds a survey to the session
     */
    public void addSurveyPresented(Integer survey) {
        if (surveysPresented == null){
            surveysPresented = new HashMap();
        }
        surveysPresented.put(survey, survey);
    }

    /**
     * Removes a survey from the session
     */
    public void removeSurveyPresented(Integer surveyID) {
        surveysPresented.remove(surveyID);
    }

    /**
     * Returns IDs of all Surveys presented during this session as a Collection of Integers
     */
    public Collection getSurveySubmittedIds() {
        return surveysSubmitted.keySet();
    }

    /**
     * Returns a map of all Surveys submitted during this session keyed on Integer id
     */
    public Collection getSurveysSubmitted() {
        return surveysSubmitted.values();
    }

    /**
     * Called when a user has submitted (filled in) a survey.
     */
    public void addSurveySubmitted(Integer survey) {
        if (surveysSubmitted == null){
            surveysSubmitted = new HashMap();
        }
        surveysSubmitted.put(survey, survey);
    }

    /**
     * Removes a survey from the session
     */
    public void removeSurveySubmitted(Integer surveyID) {
        surveysSubmitted.remove(surveyID);
    }


    public void setAttribute(String key, Object value) {
        //Avoid logging PAN
        if (!(key.equalsIgnoreCase("cvv2"))){
            log.debug("setting attribute key: " + key + " to: " + value);
        }
        sessionVars.put(key, value);
    }

    public Object getAttribute(String key) {
        //Avoid logging PAN
        if (!(key.equalsIgnoreCase("cvv2"))){
            log.debug("Get on key: " + key + " returning: " + sessionVars.get(key));
        }
        return sessionVars.get(key);
    }

    public void removeAttribute(String key) {
        sessionVars.remove(key);
    }

    //todo:huw to support expression groups
    public Collection getCampaignPKList() {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    //todo:huw to support expression groups
    public Collection getSurveyIDsSubmitted() {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    /**
     * Returns the amount of elapsed time in the current session.
     *
     * @return timeElapsed.
     */
    public BigDecimal getTimeOnSite() {
        return new BigDecimal(System.currentTimeMillis() - sessionStartTime);
    }

    /**
     * Returns an iterator over the key names
     */
    public Iterator iterateAttributes() {
        return sessionVars.keySet().iterator();
    }

    /**
     * Call this method  at the beginjing of each request.
     */
    public void setLastRequestStartTime(long time) {
        lastRequestStartTime = time;
    }

    /**
     * Call this method at the end of each request but before
     * logging REQUEST_FINISH event.
     */
    public void setLastRequestFinishTime(long time) {
        lastRequestFinishTime = time;
    }

    /**
     * Returns the sessions Locale Object.
     * @return Locale Object
     */
    public Locale getLocale() {
        return locale;
    }

    /**
     * Set the sessions Locale.
     * @param locale The sessions locale
     */
    public void setLocale(Locale locale) {
        this.locale = locale;
    }

    /**
     * Defines if this session was finalized.
     */
    public boolean isFinalized() {
        return finalized;
    }

    /**
     * Sets finalized flag for this session.
     */
    public void setFinalized(boolean inFinalized) {
        finalized = inFinalized;
    }

    /**
     * Checks if it's time for the session to time out.
     */
    public boolean isExpired() {
        boolean expired = true;
        // todo:huw
        /*
        if (System.currentTimeMillis() < (getHttpSession().getMaxInactiveInterval() * 1000 + getHttpSession().getLastAccessedTime())) {
            expired = false;
        } */
        return expired;
    }

    /**
     * Called by CommerceSessionManager at the beginning of each request
     */
    public void startRequest() {
        customer = null;
        lastRequestStartTime = System.currentTimeMillis();
    }

    private IAccountManager getAccountManager() {
        return getCommerceSessionManager().getAccountManager();
    }

    public void setCurrentRequest(HttpServletRequest currentRequest) {
        this.currentRequest = currentRequest;
    }

    public HttpServletRequest getCurrentRequest() {
        return currentRequest;
    }

	public String getABTestID() {
		return abtestID;
	}

	public void setABTestID(String abtestID) {
		this.abtestID = abtestID;
	}

	public String getABTestVersionID() {
		return abtestVersionID;
	}

	public void setABTestVersionID(String abtestVersionID) {
		this.abtestVersionID = abtestVersionID;
	}
    
	public int getABTestDuration(){
		return abtestDuration;
	}
    
	public void setABTestDuration(int abtestDuration){
		this.abtestDuration = abtestDuration;
	}
	
}

